﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XcoAppSpaces.Contracts.Serialization;
using XcoAppSpaces.Contracts.Exceptions;
using System.Reflection;
using XcoAppSpaces.Core.Ports;
using XcoAppSpaces.Core.Ports.Utils;

namespace XcoAppSpaces.Core.Communication
{
	static class SerializableCheck
	{
		public static void CheckIfMessageTypesCanBeSerialized<TContract>(this IXcoSerializer serializer) where TContract : IPort, new()
		{
			var messageTypes = GetTypesForSerializationCheck<TContract>();

			bool error = false;
			string errorInfo = "";
			foreach (var msgType in messageTypes)
			{
				string info;
				if (!serializer.CanTypeBeSerialized(msgType, out info))
				{
					error = true;
					errorInfo += info + "; ";
				}
			}

			if (error)
				throw new XcoSerializationException("One or more of the Worker's message types cannot be serialized with the used serializer! Details: " + errorInfo);
		}

		internal static IEnumerable<Type> GetTypesForSerializationCheck<TContract>() where TContract : IPort, new()
		{
			Type[] messageTypes = null;
			var workerContract = new TContract();
			if (workerContract is IPortSet)
				messageTypes = ((IPortSet)workerContract).GetMessageTypes();
			else
				messageTypes = new[] { workerContract.MessageType };

			//also extract the message types of the response ports (message types of all public fields and properties that implement IPort)
			var responsePortTypes = messageTypes
				.SelectMany(t => TypeIsPortType(t) ? new[]{t} :
					t.GetRuntimeFields().Where(f => !f.IsStatic && f.IsPublic).Select(f => f.FieldType)
					.Union(t.GetRuntimeProperties().Where(p => p.GetMethod != null && !p.GetMethod.IsStatic && p.GetMethod.IsPublic).Select(p => p.PropertyType))
					.Where(TypeIsPortType))
				.SelectMany(PortHelpers.GetMessageTypes);

			return messageTypes.Concat(responsePortTypes).Where(TypeIsNotPortType); //don't check port types themselves - they are serialized with a surrogate, so they pass despite of being not serializable
		}

		internal static bool TypeIsPortType(Type t)
		{
			return typeof(IPort).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo());
		}

		internal static bool TypeIsNotPortType(Type t)
		{
			return !TypeIsPortType(t);
		}
	}
}
